/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package algoritmo2;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Ra
 */
public class Arbol {

    private String nombre;
    private Arbol hd;
    private Arbol hi;
    private Arbol ramaCondicionIf;
    private Arbol padre;
    private int profundidad;
    private int numNodos;
    private boolean hoja;
    private boolean raiz;
    private boolean esHi;
    private boolean esHd;
    private int profTotal;

    public Arbol getRamaCondicionIf() {
        return ramaCondicionIf;
    }

    public void setRamaCondicionIf(Arbol ramaCondicionIf) {
        this.ramaCondicionIf = ramaCondicionIf;
    }

    public int getProfTotal() {
        return profTotal;
    }

    public void setProfTotal(int profTotal) {
        this.profTotal = profTotal;
    }

    public int getNumNodos() {
        return numNodos;
    }

    public void setNumNodos(int numNodos) {
        this.numNodos = numNodos;
    }

    public boolean isEsHi() {
        return esHi;
    }

    public void setEsHi(boolean esHi) {
        this.esHi = esHi;
    }

    public Arbol getHd() {
        return hd;
    }

    public void setHd(Arbol hd) {
        this.hd = hd;
    }

    public Arbol getHi() {
        return hi;
    }

    public void setHi(Arbol hi) {
        this.hi = hi;
    }

    public boolean isHoja() {
        return hoja;
    }

    public void setHoja(boolean hoja) {
        this.hoja = hoja;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public Arbol getPadre() {
        return padre;
    }

    public void setPadre(Arbol padre) {
        this.padre = padre;
    }

    public int getProfundidad() {
        return profundidad;
    }

    public void setProfundidad(int profundidad) {
        this.profundidad = profundidad;
    }

    public boolean isRaiz() {
        return raiz;
    }

    public void setRaiz(boolean raiz) {
        this.raiz = raiz;
    }

    public boolean isEsHd() {
        return esHd;
    }

    public void setEsHd(boolean esHd) {
        this.esHd = esHd;
    }

//----------------------------------------------------------------
    public Arbol() {
        nombre = null;
        hd = null;
        hi = null;
        profundidad = 0;
        numNodos = 1;
        hoja = false;
        raiz = true;
        esHi = false;
        esHd = false;
    }
//----------------------------------------------------------------

    /**
     * Metodo que iniciliza el arbol
     * @param cjtoFuns Conjunto de funciones que podran aparecer en el arbol
     * @param cjtoTerms Conjunto de terminos que podran aparecer en el arbol
     * @param hmax Altura maxima del arbol
     * @param prof profundidad en la que vamos, para iniciar el arbol este parametro debera
     * valer 0
     * @param pater Arbol padre, si se esta llamando por primera vez este parametro debera
     * ser null
     * @param esHizq si es hijoIzq Si se esta inicializando este parametro debera ser false
     * @param esRaiz Si es raiz. Si se esta inicializando este parametro debera ser true
     * @param completa Si se desea la el metodo de inicializacion completo o creciente. Si se
     * desea completo debera ser true, en otro caso false
     */
    public Arbol(List<String> cjtoFuns, List<String> cjtoTerms,
            int hmax, int prof, Arbol pater, boolean esHizq, boolean esRaiz,boolean esHde, boolean completa) {

        int nuevaProf = prof + 1;

        boolean rnd = RandomPropio.boolRandom();
        profundidad = prof;
        padre = pater;
        esHi = esHizq;
        raiz = esRaiz;
        esHd = esHde;
        if (padre == null) {
            raiz = true;
        }
        numNodos = 1;
        //si queremos la inicializacion completa
        if(completa){
            rnd = false;
        }
        if (esRaiz) {
            int intRand2 = RandomPropio.intRandom(0, cjtoFuns.size());
            
            nombre = cjtoFuns.get(intRand2);
            if (nombre.equals("NOT")) {
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true, completa);
                numNodos = numNodos + hd.getNumNodos();
            } else if (nombre.equals("IF")) {
                
                int intRand = RandomPropio.intRandom(0, cjtoTerms.size());
               
                /**Hacemos esto porque no tiene sentido añadir en la rama de la condicion del
                 * if que aparezcan los terminos D0, D1,D2,D3
                 */
                ArrayList cjtoTerminosIf = new ArrayList();
                cjtoTerminosIf.add("A0");
                cjtoTerminosIf.add("A1");
                ramaCondicionIf = new Arbol(cjtoFuns, cjtoTerminosIf, hmax, nuevaProf, this, false, false,false, completa);

                hi = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, true, false,false,completa);
                numNodos = numNodos + hi.getNumNodos();
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true,completa);
                numNodos = numNodos + hd.getNumNodos();
            } else {
                hi = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, true, false,false,completa);
                numNodos = numNodos + hi.getNumNodos();
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true,completa);
                numNodos = numNodos + hd.getNumNodos();
            }
            hoja = false;
        } else if ((rnd) || (profundidad + 1 >= hmax)) {
            /*if(esHde){
                //compruebo para que no se generen nodos malos
                int intRand = RandomPropio.intRandom(0, cjtoTerms.size());
                nombre = cjtoTerms.get(intRand);
                while(padre.hi!= null && padre.hi.getNombre().equals(nombre)){
                    intRand = RandomPropio.intRandom(0, cjtoTerms.size());
                    nombre = cjtoTerms.get(intRand);
                }                
            }
            else{*/
                int intRand = RandomPropio.intRandom(0, cjtoTerms.size());
                nombre = cjtoTerms.get(intRand);
            /*}*/
            hoja = true;
        } else {
            int intRand2 = RandomPropio.intRandom(0, cjtoFuns.size());
           
            nombre = cjtoFuns.get(intRand2);
            if (nombre.equals("NOT")) {
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true,completa);
                numNodos = numNodos + hd.getNumNodos();
            } else if (nombre.equals("IF")) {
                
                int intRand = RandomPropio.intRandom(0, cjtoTerms.size());
                //nombre = cjtoTerms.get(intRand);
                /**Hacemos esto porque no tiene sentido añadir en la rama de la condicion del
                 * if que aparezcan los terminos D0, D1,D2,D3
                 */
                ArrayList cjtoTerminosIf = new ArrayList();
                cjtoTerminosIf.add("A0");
                cjtoTerminosIf.add("A1");
                ramaCondicionIf = new Arbol(cjtoFuns, cjtoTerminosIf, hmax, nuevaProf, this, false, false,false,completa);

                hi = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, true, false,false,completa);
                numNodos = numNodos + hi.getNumNodos();
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true,completa);
                numNodos = numNodos + hd.getNumNodos();
            } else {
                hi = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, true, false,false,completa);
                numNodos = numNodos + hi.getNumNodos();
                hd = new Arbol(cjtoFuns, cjtoTerms, hmax, nuevaProf, this, false, false,true,completa);
                numNodos = numNodos + hd.getNumNodos();
            }
            hoja = false;


        }
    }

    public Arbol Clone(Arbol padreN){
        Arbol arbol = new Arbol();
        arbol.setEsHi(this.esHi);
        arbol.setHoja(this.hoja);
        arbol.setEsHd(this.esHd);
        arbol.setNombre(this.nombre);
        arbol.setNumNodos(this.numNodos);
        arbol.setPadre(padreN);
        arbol.setProfTotal(this.profTotal);
        arbol.setProfundidad(this.profundidad);
        arbol.setRaiz(this.raiz);
        if(hd != null){
            arbol.setHd(this.hd.Clone(arbol));
        }
        if(hi != null){
            arbol.setHi(this.hi.Clone(arbol));
        }
        if(ramaCondicionIf != null){
            arbol.setRamaCondicionIf(this.ramaCondicionIf.Clone(arbol));
        }
        return arbol;
    }

    public Arbol nodoAleatorio() {
        return null;
    }

    
    public String toString(String concat) {

        String text = "\n"+concat+"[nombre:" + nombre +"]";
         if(nombre.equals("IF")){
             text+= ramaCondicionIf.toString(concat + "\tCondición:");
         }
        if (hi != null) {
            text += hi.toString(concat + "\t");
        }
        if (hd != null) {
            text += hd.toString(concat + "\t");
        }

        return text;
    }

    public int evalua(Map valores) {

        int valor = 0;
        if (!hoja) {
            if (nombre.equals("NOT")) {
                //evaluar not
                valor = evalNOT(valores);
            } else if (nombre.equals("OR")) {
                //evaluar or
                valor = evalOR(valores);
            } else if (nombre.equals("AND")) {
                //evaluar and
                valor = evalAnd(valores);
            } else if (nombre.equals("IF")) {
                //evaluar if
                valor = evalIF(valores);
            }
            //System.out.println("Devolviendo " + valor);
        } else {
            try{
            valor = (Integer) valores.get(nombre);
            }
            catch(Exception e){
                System.out.println(nombre);
                e.printStackTrace();
            }
        }
        return valor;
    }

    private int evalNOT(Map valores) {

        int valor = hd.evalua(valores);
        //System.out.println("Evaluando la funcion NOT con valor del hijo " + valor);
        valor = (valor + 1) % 2;

        return valor;
    }

    private int evalOR(Map valores) {
        int valorI = hi.evalua(valores);
        int valorD = hd.evalua(valores);
        //System.out.println("Evaluando la funcion OR con valor del hijoI: " + valorI + " y valor hijoD: " + valorD);
        if (valorI == 1 || valorD == 1) {
            return 1;
        } else {
            return 0;
        }
    }

    private int evalAnd(Map valores) {
        int valorI = hi.evalua(valores);
        int valorD = hd.evalua(valores);
        //System.out.println("Evaluando la funcion AND con valor del hijoI: " + valorI + " y valor hijoD: " + valorD);
        if (valorI == 1 && valorD == 1) {
            return 1;
        } else {
            return 0;
        }
    }

    /**
     * Evalua la condicion if(en el if si la condicion es verdadera coge la rama izquierda
     * y si es falsa pues la rama derecha)
     * @param valores
     * @return
     */
    private int evalIF(Map<String, Integer> valores) {
        int resultadoCondicion = ramaCondicionIf.evalua(valores);
        //System.out.println("Evaluando la funcion IF con valor de la condicion: " + nombre[1] + ":" +valores.get(nombre[1]));
        if (resultadoCondicion == 1) {
            //System.out.println("la condicion ha sido verdadera se ira por la rama izquierda");
            return hi.evalua(valores);
        } else {
            //System.out.println("la condicion ha sido falsa se ira por la rama derecha");
            return hd.evalua(valores);
        }
    }

    public void reestablecerProfundidad(int i) {
        
            this.profundidad = i;
            
                   
            if(this.hd != null){
                this.hd.reestablecerProfundidad(i+1);
            }
            if(this.hi != null){
               this.hi.reestablecerProfundidad(i+1);
            }
            if(this.ramaCondicionIf != null){
                this.ramaCondicionIf.reestablecerProfundidad(i+1);
            }
           
    }

    public int reestablecerNumNodos() {
        if(hoja){
            this.numNodos = 0;
            return 1;
        }
        else{
            ArrayList<Integer> profundidades = new ArrayList<Integer>();
            if(this.hd != null){
                profundidades.add(this.hd.reestablecerNumNodos());
            }
            if(this.hi != null){
                profundidades.add(this.hi.reestablecerNumNodos());
            }
            if(this.ramaCondicionIf != null){
                profundidades.add(this.ramaCondicionIf.reestablecerNumNodos());
            }
            int max = 0;
            for(int i = 0; i < profundidades.size(); i++){
                if(max < profundidades.get(i)){
                    max = profundidades.get(i);
                }
            }
            this.numNodos = max;
            return max +1;
        }
    }

    public  boolean comprobarBuenaConstruccionArbol() {
        if(hoja){
            return false;
        }
        else{

            if(nombre.equals("AND") || nombre.equals("OR")){
                boolean resul1 = hi.comprobarBuenaConstruccionArbol();
                boolean resul2 = hd.comprobarBuenaConstruccionArbol();
                if(resul1 || resul2){
                    return true;
                }
                else{
                    if(hi.isHoja() &&  hd.isHoja() && hi.getNombre().equals(hd.getNombre())){
                        return true;
                    }
                }
                return false;
            }
            else if(nombre.equals("IF")){
                boolean resul1 = hi.comprobarBuenaConstruccionArbol();
                boolean resul2 = hd.comprobarBuenaConstruccionArbol();
                boolean resul3 = ramaCondicionIf.comprobarBuenaConstruccionArbol();
                if(resul1||resul2||resul3){
                    return true;
                }
                else{
                    if(hi.isHoja() &&  hd.isHoja() && hi.getNombre().equals(hd.getNombre())){
                        return true;
                    }
                    return false;
                }
            }
            else {
                if(nombre.equals(hd.getNombre())){
                    return true;
                }
                return hd.comprobarBuenaConstruccionArbol();
            }
        }
    }
}
